#include "OwinHost.h"
#include "ApplicationException.h"
#include "InteropService.h"
#include "platform.h"

#include <comdef.h>
#include <metahost.h>
#include <mscoree.h>
#pragma comment(lib, "mscoree.lib") 

#include <stdexcept>
#include <string>
#include <sstream>
#include <direct.h>
using namespace std;


#define CHECK_ERR(hr) if(FAILED(hr)) throw ApplicationException(_com_error(hr).ErrorMessage());
#define OWIN_ASM_NAME L"SAP.BusinessOne.Cockpit.Owin.dll"
#define OWIN_TYPE_NAME L"SAP.BusinessOne.Cockpit.Owin.CockpitServer"
#define OWIN_START_NAME L"Start"
#define OWIN_STOP_NAME L"Stop"
#define OWIN_PORT_NAME L"GetAvailablePort"
#define OWIN_PARAM L""

unique_ptr<OwinHost> OwinHost::s_instance;
OwinHost::OwinHost(void)
	: m_clr(nullptr)
{
	CoInitializeEx(NULL, COINIT_MULTITHREADED);

	// TODO: we have to use the path that core-native.dll exists instead of current directory
	wchar_t buff[MAX_PATH];
	_wgetcwd(buff, MAX_PATH);
	
	// path
	wstringstream path;
	path << buff;
	path << '\\';
	path << OWIN_ASM_NAME;
	m_owin = path.str();
}
OwinHost::~OwinHost(void)
{
	CoUninitialize();
}
OwinHost& OwinHost::instance()
{
	if(!s_instance)
	{
		s_instance.reset(new OwinHost());
	}
	return *s_instance;
}
void OwinHost::startup(IContext* env)
{
	if(!env)
	{
		throw runtime_error("Invalid pointer of IContext");
	}
	InteropService::set(env);

	startupCLR();

	// get startup url
	unsigned short port = getAvailablePort();
	wstringstream ss;
	ss << L"http://localhost:" << port;
	m_root = ss.str();
	m_startupUrl = m_root + L"/sap/sbo/cockpit/index.html";

	startupOwin();
}
void OwinHost::shutdown()
{
	shutdownOwin();
	shutdownCLR();
}
const wstring& OwinHost::root() const
{
	return m_startupUrl;
}
void OwinHost::shutdownCLR()
{
	if(m_clr)
	{
		ICLRRuntimeHost *pClrHost = static_cast<ICLRRuntimeHost*>(m_clr);

		HRESULT hr = pClrHost->Stop();
		CHECK_ERR(hr);

		pClrHost->Release();
		m_clr = NULL;        
	}
}
void OwinHost::startupCLR()
{
	ICLRRuntimeHost *pClrHost = nullptr;
	ICLRRuntimeInfo *lpRuntimeInfo = nullptr;
	ICLRMetaHost *lpMetaHost = nullptr;

	try
	{
		HRESULT hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&lpMetaHost));
		CHECK_ERR(hr);

		hr = lpMetaHost->GetRuntime(L"v4.0.30319", IID_PPV_ARGS(&lpRuntimeInfo));
		CHECK_ERR(hr);

		hr = lpRuntimeInfo->GetInterface(CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (LPVOID *)&pClrHost);
		CHECK_ERR(hr);

		hr = pClrHost->Start();
		CHECK_ERR(hr);
	}
	catch(const exception& e)
	{
		if(pClrHost) pClrHost->Release();
		if(lpRuntimeInfo) lpRuntimeInfo->Release();
		if(lpMetaHost) lpMetaHost->Release();
		throw e;
	}

	lpRuntimeInfo->Release();
	lpMetaHost->Release();

	// save
	m_clr = pClrHost;
}
void OwinHost::startupOwin()
{
	if(!m_clr)
	{
		throw runtime_error("CLR has not been started");
	}

	ICLRRuntimeHost *pClrHost = static_cast<ICLRRuntimeHost*>(m_clr);
	DWORD dwRet;

	HRESULT hr = pClrHost->ExecuteInDefaultAppDomain(
		m_owin.c_str(),
		OWIN_TYPE_NAME, 
		OWIN_START_NAME, 
		m_root.c_str(), 
		&dwRet);
	CHECK_ERR(hr);

	if(dwRet != 0)
	{
		throw runtime_error("Start owin host failed");
	}
}
void OwinHost::shutdownOwin()
{
	if(!m_clr)
	{
		throw runtime_error("CLR has not been started");
	}

	ICLRRuntimeHost *pClrHost = static_cast<ICLRRuntimeHost*>(m_clr);
	DWORD dwRet;

	HRESULT hr = pClrHost->ExecuteInDefaultAppDomain(
		m_owin.c_str(),
		OWIN_TYPE_NAME, 
		OWIN_STOP_NAME, 
		OWIN_PARAM, 
		&dwRet);
	CHECK_ERR(hr);

	if(dwRet != 0)
	{
		throw runtime_error("Stop owin host failed");
	}
}
unsigned short OwinHost::getAvailablePort()
{
	if(!m_clr)
	{
		throw runtime_error("CLR has not been started");
	}

	ICLRRuntimeHost *pClrHost = static_cast<ICLRRuntimeHost*>(m_clr);
	DWORD dwRet;

	HRESULT hr = pClrHost->ExecuteInDefaultAppDomain(
		m_owin.c_str(),
		OWIN_TYPE_NAME, 
		OWIN_PORT_NAME, 
		OWIN_PARAM, 
		&dwRet);
	CHECK_ERR(hr);

	if(dwRet == 0)
	{
		throw runtime_error("Failed to get available port");
	}

	return static_cast<unsigned short>(dwRet);
}